package org.uva.streambuilder;

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Observable;
import java.util.Observer;

public abstract class StreamModule extends Observable implements 
 	MouseListener, MouseMotionListener, Observer
{
    int width, height;
    int x, y;
    int mx, my;
    int cx, cy;
    boolean isMouseDraggingBox = false, mouseOver = false;
    int bx = 80, by = 50; // block size
    int portHeight = 10; 
    int textTopMargin = 15 + portHeight;
    int textLeftMargin = 10;
    Applet applet;
    static int snap = 10;
    String name;
    Font font;
    Color selectColor;
    Color defaultColor;
    Color inputColor;
    Color outputColor;
    
    double value = 0f;
    
    int startx, starty;
    boolean creatingConnector = false;
    boolean connectorValid = false;
    boolean isSelected = false;
    
    int numInputs = 1;
    int numOutputs = 1;
    
    int border = 5;
    static ArrayList<StreamModule> moduleList = null;
    ArrayList<StreamConnector> outputConnectors = null;
    ArrayList<StreamConnector> inputConnectors = null;
    
    static int id = 0;

    public StreamModule (Applet app, int x, int y)
    {
    	if (moduleList == null)
    		moduleList = new ArrayList<StreamModule>();
    	
	id++;
	name = "Module " + id;
	applet = app;
	this.x = x-bx/2; this.y = y-by/2;
	applet.addMouseListener( this );
	applet.addMouseMotionListener( this );

	font = new Font("Monospace", Font.PLAIN, 10);
	defaultColor = Color.gray;
	selectColor = Color.red;
	inputColor = Color.gray;
	outputColor = Color.gray;

	moduleList.add(this);
	outputConnectors = new ArrayList<StreamConnector>();
	inputConnectors = new ArrayList<StreamConnector>();
	
    }    
    
    boolean isSelectingInput(MouseEvent e)
    {
    	int mouseX = e.getX();
    	int mouseY = e.getY();
    	
    	if ( x < mouseX && mouseX < x + bx && y < mouseY && mouseY < y + portHeight)
    		return true;
    	else
    		return false;
    }
    
    boolean isSelectingOutput(MouseEvent e)
    {
    	int mouseX = e.getX();
    	int mouseY = e.getY();
    	
    	if ( x < mouseX && mouseX < x + bx && y+portHeight + by < mouseY && mouseY < y + 2*portHeight + by)
    		return true;
    	else
    		return false;
    }
    
    boolean isSelectingBox(MouseEvent e)
    {
    	int mouseX = e.getX();
    	int mouseY = e.getY();
    	
    	if ( x < mouseX 
                && mouseX < x + bx 
                && y+portHeight < mouseY 
                && mouseY < y + portHeight + by)
    		return true;
    	else
    		return false;
    }
    
    
    boolean isClicking(int mx, int my)
    {
	if ( x < mx + border && mx < x+bx + border && y < my + border && my < y+by+border)
	    return true;
	else
	    return false;
    }

    public void drawBorderedRect(Graphics g, int x, int y, int bx, int by, Color color)
    {
    	g.setColor(color);
    	g.fillRect(x,y,bx,by);
    	g.setColor(Color.black);
    	g.drawRect(x,y,bx,by);
    }
    
    public void drawPorts( Graphics g )
    {
    	drawBorderedRect(g, x, y, bx, portHeight, inputColor);
    	drawBorderedRect(g, x, y+portHeight+by, bx, portHeight, outputColor);
    }
    
    public void drawConnector( Graphics g )
    {
    	if (creatingConnector)
    	{
    		int ymid = (cy-starty)/2 + starty;
    		if (connectorValid)
    			g.setColor(Color.green);
    		else
    			g.setColor(Color.white);
    		
    		g.drawLine(startx, starty, startx, ymid);
    		g.drawLine(startx, ymid, cx, ymid);
    		g.drawLine(cx, ymid, cx, cy);	
    	}
    }

    public void draw( Graphics g )
    {
	if (isSelected)
	    g.setColor( selectColor );
	else 
	    g.setColor( defaultColor );
	g.fillRect( x, y+portHeight, bx, by );

	g.setColor( Color.black );
	g.drawRect( x, y+portHeight, bx, by );
	g.setFont(font);
	g.drawString(name , x + textLeftMargin, y + textTopMargin );
	double dispval = Math.floor(value * 100)/100;
	g.drawString(Double.toString(dispval), x + textLeftMargin, y + 2* textTopMargin);
	
	drawPorts(g);
	drawConnector(g);
	
	for (StreamConnector o : outputConnectors ){
			o.draw(g);
	}
		
		
    }

    public void mouseEntered( MouseEvent e ) {}
    public void mouseExited (MouseEvent e ) {}
    public void mouseClicked( MouseEvent e ) {}
    public void mousePressed ( MouseEvent e ) {
	mx = e.getX();
	my = e.getY();
	cx = mx;
	cy = my;
	
	if ( isSelectingBox(e)) {
	    isMouseDraggingBox = true;
	}
	
	if (!creatingConnector && (isSelectingInput(e) || isSelectingOutput(e))) {
		creatingConnector = true;
		startx = mx;
		starty = my;
	}
	
	
	e.consume();
    }

    public void mouseReleased (MouseEvent e ) {
	x = x - (x%snap);
	y = y - (y%snap);
	isMouseDraggingBox = false;
	
	if (creatingConnector) {
		
		for (StreamModule m : moduleList)
		{
			if (m.isSelectingInput(e)){
				StreamConnector conn = new StreamConnector(this, m);
				this.addObserver(conn.end);
			}
		}
		
		creatingConnector = false;
		startx = 0; starty = 0;
	}
		
	e.consume();
    }
    public void mouseMoved( MouseEvent e ) {
    	if (isClicking(e.getX(), e.getY()))
    		mouseOver = true;
    	else
    		mouseOver = false;
    	
    	if (creatingConnector)
    	{
    		
    	for (StreamModule m : moduleList)
		{
			if (m.isSelectingInput(e)){
				connectorValid = true;
			}
			else{
				connectorValid = false;	
			}
		}
    	}
 
    	
    }

    public void mouseDragged( MouseEvent e ) {
    	cx = e.getX();
    	cy = e.getY();
    	
    	
    	
	if (isMouseDraggingBox ) {
	    int new_mx = e.getX();
	    int new_my = e.getY();

	    x += new_mx - mx;
	    y += new_my - my;

	    mx = new_mx;
	    my = new_my;
	    
	    Collections.sort(this.outputConnectors);
	     

	    e.consume();
	}
    }

	public int getOutputPositionX(int index) {
		return x +  index*bx/numOutputs;
	}

	public int getOutputPositionY() {
		return y + 2*portHeight + by;
	}

	public int getInputPositionX(int index) {
		return x +  index*bx/numInputs;
	}
	
	public int getInputPositionY() {
		return y;
	}
	
	public void cleanup()
	{
		for (StreamConnector c : this.inputConnectors)
		{
			c.begin.outputConnectors.remove(c);
		}
		
	}

	public abstract void update(Observable o, Object arg);

	public double getValue() {
		return value;
	}

	public void setValue(double value) {
		this.value = value;
		setChanged();
		notifyObservers(value);
	}
	
	public static ArrayList<StreamModule> getModuleList()
	{
		if (moduleList==null)
			moduleList = new ArrayList<StreamModule>();
		
		return moduleList;
		
	}
    
    
}
